=begin
    This file is part of GSRuby.

    GSRuby is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    GSRuby is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with GSRuby.  If not, see <http://www.gnu.org/licenses/>.
=end

require 'net/https'
require 'openssl'
require 'json'

module GNUSocial

# This class manages calls to GNU Social API
class Client

    # Creates and configure a Client for make calls to GNU Social API.
    #
    # Arguments:
    #   node: GNU Social instance
    #   user: Username
    #   passwd: Password
    #
    #   Optional:
    #       ssl: Whether to use SSL, default false
    #       ssl_insecure: If is true, SSL cert will not be verified
    #       proxy_host: Proxy url used for the connection
    #       proxy_port: Proxy port used for the connection
    #       source: This is used for identify your app, default 'api'
    def initialize(node, user, passwd, options = {})
        node = URI(node)
        if options[:proxy_host] && options[:proxy_port]
            proxy = [options[:proxy_host],options[:proxy_port]]
        end
        @node = Net::HTTP.new(node.hostname,node.port,*proxy)
        @user = user
        @passwd = passwd
        @source = options[:source] ? options[:source] : 'api'
        @node.use_ssl = true if options[:ssl]
        @node.verify_mode = OpenSSL::SSL::VERIFY_NONE if options[:ssl_insecure]
    end

    # Makes a request to the GNU Social instance.
    #
    # Arguments:
    #   path: Path to do the connection
    #   params: A hash containing additional parameters
    def connect(path, params)
        request = Net::HTTP::Post.new(path)
        request.basic_auth(@user,@passwd)
        request.set_form_data(params)
        res = @node.request(request)
    end

    # Makes a request to GNU Social instance and returns a Timeline object.
    #
    # Arguments:
    #   path: Path to do the connection
    #   options: A hash containing additional parameters, that can be:
    #       since: Notice ID (integer). Request will return newer notices than the specified
    #       before: Notice ID (integer). Request will return older notices than the specified
    #       count: Integer. Number of notices that will be returned
    #       page: Number of page that will be returned, 1 page == 20 notices
    def get_timeline(path, options = {})
        params = {}
        params[:since_id] = options[:since] if options[:since]
        params[:max_id] = options[:before] if options[:before]
        params[:count] = options[:count] if options[:count]
        params[:page] = options[:page] if options[:page]
        Timeline.from_json(connect(path,params).body)
    end

    # Gets the instance Public Timeline.
    #
    # Arguments:
    #   options: Check get_timeline options
    def public_timeline(options = {})
        get_timeline('/api/statuses/public_timeline.json',options)
    end

    # Gets the user's Home Timeline.
    #
    # Arguments:
    #   options: Check get_timeline options
    def home_timeline(options = {})
        get_timeline('/api/statuses/home_timeline.json',options)
    end

    # Gets the user's mentions.
    #
    # Arguments:
    #   options: Check get_timeline options
    def mentions(options = {})
        get_timeline('/api/statuses/mentions.json',options)
    end

    #def user_timeline(user, options = {})
    #end

    # Gets the full conversation of the given Notice.
    #
    # Arguments:
    #   notice: NoticeID (integer) or Notice object
    #   options: Check get_timeline options
    def conversation(notice, options = {})
        id = get_id(notice)
        get_timeline("/api/statusnet/conversation/#{id}.json",options)
    end

    #Returns the notice with the given ID.
    #
    # Arguments:
    # id: ID of the notice to be returned
    def notice(id)
        res = @node.get("/api/statuses/show/#{id.to_s}.json")
        Notice.new(JSON.parse(res.body))
    end

    # Publish a new status.
    #
    # Arguments:
    #   msg: Message to be published
    #   options: A hash containing additional parameters, that can be:
    #       reply: Publish as reply of the given NoticeID (integer) or Notice object
    #       source: Change the source of the status, default is source property
    def send_status(msg, options = {})
        params = {:status => msg}
        params[:in_reply_to_status_id] = options[:reply] if options[:reply]
        params[:source] = options[:source] ? options[:source] : @source
        connect('/api/statuses/update.json',params)
    end

    # Return the ID of the given notice.
    def get_id(notice)
        if notice.is_a? Fixnum
            return notice
        elsif notice.is_a? Notice
            return notice.id
        else
            raise "Must be a integer or a Notice"
        end
    end

    # Publish a reply to the given Notice.
    #
    # Arguments:
    #   msg: Message to be published
    #   notice: NoticeID (integer) or Notice object
    #   options: Check send_status options
    def reply(msg, notice, options = {})
        options[:reply] = get_id(notice)
        send_status(msg,options)
    end

    # Marks the given notice as favorite.
    #
    # Arguments:
    #   notice: Notice to be marked as fav
    def fav(notice)
        id = get_id(notice)
        params = {:id => id}
        connect("/api/favorites/create/#{id}.json",params)
    end

    # Repeat the given notice.
    #
    # Arguments:
    #   notice: Notice to be repeated
    #   options: A hash containing additional parameters, that can be:
    #      source: Change the source of the status, default is source property
    def repeat(notice, options = {})
        id = get_id(notice)
        params = {:id => id}
        params[:source] = options[:source] ? options[:source] : @source
        connect("/api/statuses/retweet/#{id}.json",params)
    end

    def delete(notice)
        connect('',params)
    end

end

# Represents Timeline or group of notices.
class Timeline

    # Creates a Timeline object from JSON response.
    def self.from_json(json)
        notices = []
        JSON.parse(json).each do |notice|
            notices.push(Notice.new(notice))
        end
        Timeline.new(notices)
    end

    # Creates a new Timeline object
    def initialize(notices)
        @notices = notices
    end

    # Return the notice with the given index on this timeline.
    def notice(index)
        return 'Index must be a integer' unless index.is_a?(Fixnum)
        (0..size-1).cover?(index) ? @notices[index] : 'Bad index'
    end

    # Returns the number of notices on this timeline.
    def size
        @notices.size
    end

    # Return the most recent notice on this timeline.
    def newest_notice
        @notices[0]
    end

    # Returns the less recent notice on this timeline.
    def oldest_notice
        @notices[-1]
    end

    # Calls the given block for each notice on this timeline.
    def notices
        return "Need a block" unless block_given?
        @notices.each do |n|
            yield(n)
        end
    end

end

# Represents a status or activity
class Notice

    attr_reader :text, :id, :created_at, :uri, :source

    # Parses and create a new Notice object from data
    def initialize(data)
        data.each do |key, value|
            case key
                when "created_at" then set_time(value)
                else instance_variable_set(:"@#{key}",value)
            end
        end
    end

    def to_s
        "#{@text} by #{@user["name"]} at #{@created_at} ID [#{@id}]"
    end

    def set_time(at)
        /\w{3} (?<mo>\w{3}) (?<d>\d+) (?<h>\d+):(?<m>\d+):(?<s>\d+) \+\d+ (?<y>\d+)/ =~ at
        @created_at = Time.utc(y,mo,d,h,m,s).getlocal
    end

    def author
        @user["name"]
    end

    def is_reply?
        @in_reply_to_status_id != nil
    end

    def reply_to
        @in_reply_to_status_id
    end

    def reply_to_name
        @in_reply_to_screen_name
    end

    def reply_to_userid
        @in_reply_to_user_id
    end

    def html
        @statusnet_html
    end

end
end
